#include "sleeabstract_bdb.h"
#include <QDebug>

SleeAbstract_BDB::SleeAbstract_BDB():
        m_bDbOk(false),
        m_pDb(NULL)
{
}


//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::qv_to_qb(QByteArray &buffer, const QVariant &qv_data)
{
    buffer.clear();
    QDataStream toBuf(&buffer, QIODevice::WriteOnly);
    toBuf<<qv_data;
}


//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::qb_to_qv(QByteArray &buffer, QVariant &qv_data)
{
    qv_data.clear();
    QDataStream fromBuf(&buffer, QIODevice::ReadOnly);
    fromBuf>>qv_data;
}



//------------------------------------------------------------------------------------------------------------


int SleeAbstract_BDB::cmpr_qvariants(const QVariant &qv_a_data, const QVariant &qv_b_data)
{
    QByteArray qb_a, qb_b;
    QVariant qv_a = qv_a_data, qv_b = qv_b_data;
    QVariant::Type type_a = qv_a.type();
    QVariant::Type type_b = qv_b.type();
    if(type_a != type_b)
    {
        qDebug()<<"cmprs different types"<<qv_a.typeName()<<qv_b.typeName();
        if(type_a > type_b) return -1;
        return 1;
    }
    switch(type_a)
    {
    case QVariant::String:
        if(qv_a.toString() > qv_b.toString()) return 1;
        if(qv_a.toString() < qv_b.toString()) return -1;
        return 0;
    case QVariant::Int:
        if(qv_a.toInt() > qv_b.toInt()) return 1;
        if(qv_a.toInt() < qv_b.toInt()) return -1;
        return 0;
    case QVariant::Double:
        if(qv_a.toDouble() > qv_b.toDouble()) return 1;
        if(qv_a.toDouble() < qv_b.toDouble()) return -1;
        return 0;
    case QVariant::ULongLong:
        if(qv_a.toULongLong() > qv_b.toULongLong()) return 1;
        if(qv_a.toULongLong() < qv_b.toULongLong()) return -1;
        return 0;
    case QVariant::Date:
        if(qv_a.toDate() > qv_b.toDate()) return 1;
        if(qv_a.toDate() < qv_b.toDate()) return -1;
        return 0;
    default:
        qv_a.convert(QVariant::ByteArray);
        qv_b.convert(QVariant::ByteArray);
        qb_a = qv_a.toByteArray();
        qb_b = qv_b.toByteArray();
        if(qb_a > qb_b) return 1;
        if(qb_a < qb_b) return -1;
        return 0;
    };
    return 0;
}


//------------------------------------------------------------------------------------------------------------

int SleeAbstract_BDB::cmpr_var_keys(Db *pDb, const Dbt *pkey1, const Dbt *pkey2)
{
    Q_UNUSED(pDb);
    bool bspec1 = isDbtSpec(pkey1);
    bool bspec2 = isDbtSpec(pkey2);
    if(bspec1 & bspec2) return 0;
    if(bspec1) return -1;
    if(bspec2) return 1;


    QVariant qv_a, qv_b;
    dbt_to_qv(pkey1, qv_a);
    dbt_to_qv(pkey2, qv_b);

    return cmpr_qvariants(qv_a, qv_b);
}

//------------------------------------------------------------------------------------------------------------

int SleeAbstract_BDB::cmpr_dup_sort(Db *pDb, const Dbt *pkey1, const Dbt *pkey2)
{
    Q_UNUSED(pDb);
    bool bspec1 = isDbtSpec(pkey1);
    bool bspec2 = isDbtSpec(pkey2);
    if(bspec1 & bspec2) return 0;
    if(bspec1) return -1;
    if(bspec2) return 1;


    QVariant qv_a, qv_b;
    dbt_to_qv(pkey1, qv_a);
    dbt_to_qv(pkey2, qv_b);
 //   qDebug()<<"cmpr dup sort"<<qv_a<<qv_b;
    return cmpr_qvariants(qv_a, qv_b);
}


//------------------------------------------------------------------------------------------------------------

int SleeAbstract_BDB::initDb(const QString &qstrHomeDir, const QString &qstrDbName,bool bReadOnlyMode)
{
    if(m_pDb != NULL) return -1;
    m_qdirHome.setPath(qstrHomeDir);
    if(!m_qdirHome.exists()) return -1;
    m_qstrDbName = qstrDbName;
    QString qstrDbFilePathName = qstrHomeDir + QDir::separator() + qstrDbName + ".db";
    qDebug()<<"main db file"<<qstrDbFilePathName;
    m_bReadOnly = bReadOnlyMode;
    m_dbOpenFlags = (!m_bReadOnly)?DB_CREATE:DB_RDONLY;

    try
    {
        m_pDb = new Db(NULL,0);
        pre_initDb();
        m_pDb->set_bt_compare(cmpr_var_keys);
        m_pDb->open(NULL,qstrDbFilePathName.toStdString().c_str(), m_qstrDbName.toStdString().c_str(), DB_BTREE, DB_CREATE, 0);
        post_initDb();
        m_pDb->cursor(NULL, &m_header_cur,0);
        read_header();
    }
    catch(DbException &e)
    {
        qCritical()<<"SleeAbstract_BDB::initDb() has caught a Db Exception"<<QString::fromUtf8(e.what());
    }

    m_bDbOk = true;
//    char *fBuf = new char[256];
//    char *dbBuf = new char[128];
//    m_pDb->get_dbname((const char **)&fBuf,(const char **)&dbBuf);
//    QString aa = fBuf;
//    QString bb = dbBuf;
    qDebug()<<"db opened at"<<m_qdirHome.absolutePath();
    return 0;
}

//------------------------------------------------------------------------------------------------------------

int SleeAbstract_BDB::closeDb()
{
    if(!m_bDbOk) return 0;
    pre_closeDb();
    if(m_pDb != NULL)
    {
        m_header_cur->close();
        m_pDb->close(0);
        delete m_pDb;
        m_pDb = NULL;
    }
    m_bDbOk = false;

    return 0;
}


//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::set_header_defaults()
{
    m_header.key_qv_type = static_cast<int>(QVariant::Invalid);
    m_header.slee_db_vers = SLEEDB_VERS;
    m_header.cntr = 0;
    m_header.fld_0 = 0;
    m_header.fld_1 = 0;
}

//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::read_header()
{
    u_int64_t header_key = SLEEHEADER_KEY;
    Dbt key(&header_key, sizeof(u_int64_t));
    Dbt data;
    int ret;
    ret = m_header_cur->get(&key, &data, DB_SET);
    if(ret)
    {
        set_header_defaults();
        data.set_data(&m_header);
        data.set_size(sizeof(SleeDbHeader));
        m_header_cur->put(&key, &data, DB_KEYFIRST);

    }
    m_header = *(SleeDbHeader *)data.get_data();
}

//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::write_header()
{
    Dbt key;
    Dbt data(&m_header, sizeof(SleeDbHeader));
    try
    {
        m_header_cur->put(&key, &data, DB_CURRENT);
    }
    catch(DbException &e)
    {
        qCritical()<<"SleeAbstract_BDB::write_header() has caught a Db Exception"<<e.what();
    }
}

//------------------------------------------------------------------------------------------------------------

QVariant SleeAbstract_BDB::genId()
{
    if(!m_bDbOk) return QVariant();
    if(m_bReadOnly) return QVariant();
    m_header.cntr++;
    if(m_header.cntr == 0x7FFFFFFFFFFFFFFF) m_header.cntr = 1;
    write_header();
    try
    {
        m_pDb->sync(0);
    }
    catch(DbException &e)
    {
         qCritical()<<"SleeAbstract_BDB::genId() has caught a Db Exception"<<e.what();
    }
    return QVariant(static_cast<quint64>(m_header.cntr));
}

//------------------------------------------------------------------------------------------------------------

bool SleeAbstract_BDB::match_key_type(QVariant &qv_key)
{
    QVariant qv_conv = qv_key;
    int i = 0;
    if(m_header.key_qv_type == 0)
    {
        m_header.key_qv_type = static_cast<u_int32_t>(qv_key.type());
        write_header();
        return true;
    }
    if(m_header.key_qv_type != (static_cast<u_int32_t>(qv_key.type())))
    {
        qWarning()<<"Warning. SleeAbstract_BDB::match_key_type(...) Different key types. Trying to match";
        int inc = 0;

        while(1)
        {

            if(i>=127) return false;
            if(qv_conv.type() == static_cast<QVariant::Type>(m_header.key_qv_type+inc)) return false;
            if(!qv_conv.convert(static_cast<QVariant::Type>(m_header.key_qv_type+inc)))
            {
                if(qv_key.type() > static_cast<QVariant::Type>(m_header.key_qv_type)) inc++;
                else inc--;
            }
            else
            {
                if(inc ==0) break;
                inc = 0;
                i++;
                continue;
            }
            qv_conv = qv_key;
            if(qv_key.type() == static_cast<QVariant::Type>(m_header.key_qv_type+inc)) return false;
        }
    }
    qv_key = qv_conv;
    return true;
}

//------------------------------------------------------------------------------------------------------------

void SleeAbstract_BDB::setId(u_int64_t id)
{
    if(!m_bDbOk) return;
    if(m_bReadOnly) return;
    m_header.cntr = id;
    write_header();
    try
    {
        m_pDb->sync(0);
    }
    catch(DbException &e)
    {
         qCritical()<<"SleeAbstract_BDB::genId() has caught a Db Exception"<<e.what();
    }
}
